#include "../echo_service.pb.h"
#include "../RpcChannel.h"
#include "../../TcpConnection.h"
#include "../../TcpClient.h"
#include "../../EventLoop.h"
#include <memory>
#include "../RpcController.h"
#include "../../Channel.h"

#include <unistd.h>
#include <rpc/types.h>
#include <strings.h>
#include <time.h>
#include <errno.h>
#include "../../util.h"

using namespace std::placeholders;

static int kRequests = 1000;
int clients = 1;
int mypipe[2];
double responseTime = 0;
int i;

class RpcClient
{
public:
    RpcClient(EventLoop* loop, const string& ip, const int port)
        : loop_(loop)
        , client_(loop, ip, port)
        , channel_(new RpcChannel)
        , stub_(channel_.get())
        , count_(0)
        , firstCall_(false)
    {
        client_.setConnectionCallback(std::bind(&RpcClient::onConnection, this, _1));
        client_.setMessageCallback(std::bind(&RpcChannel::onMessage, channel_.get(), _1, _2));
        loop_->runAfter(1, std::bind(&RpcClient::sendRequest, this));
    }

    void connect()
    {
        client_.connect();
    }

    void sendRequest() {
        // cout << "sendRequest\n";
        // if (count_ == kRequests-1)
        //     printf("%d tid %d sendRequest\n", i, count_+1);
        if (!firstCall_) {
            firstCall_ = true;
            start_ = clock();
        }
        channel_->setConnection(client_.connection());

        EchoRequest request;
        request.set_message("Hello World " + to_string(count_+1) + " num!");
        EchoResponse* response = new EchoResponse;
        RpcController cntl;
        auto donClosure = google::protobuf::NewCallback(this, &RpcClient::replied, response);
        // printf("%d tid %d Echo\n", i, count_+1);
        stub_.Echo(&cntl, &request, response, donClosure);
    }
    double timeDiff() {
        if (firstCall_) return static_cast<double>(end_ - start_);
        else return -1;
    }
private:
    void onConnection(const TcpConnectionPtr& conn) 
    {
        // std::cout << "Send your message : " << std::endl;
    }

    void replied(EchoResponse* response) {
        // printf("%d get responsen\n", count_);
        // printf("%d tid %d get responsen : %s\n", i, count_+1, response->DebugString().c_str());
        // std::cout << response->DebugString();
        ++count_;
        if (count_ < kRequests) sendRequest();
        else {
            end_ = clock();
            loop_->quit();
        }
    }

    EventLoop* loop_;
    TcpClient client_;
    RpcChannelPtr channel_;
    EchoServer_Stub stub_;

    int count_;
    bool firstCall_;
    clock_t start_;
    clock_t end_;
};




void benchcore() {
    EventLoop loop;
    std::string ip = "127.0.0.1";
    int port = 12345;

    RpcClient rpcClient(&loop, ip, port);
    
    rpcClient.connect();
    loop.loop();
    responseTime = rpcClient.timeDiff();
    // printf("%d tid done\n", i);
    return;
}

int main(int argc, char* argv[]) {

    if (argc > 1) clients = atoi(argv[1]);
    if (argc > 2) kRequests = atoi(argv[2]);

    printf("Numbers of clients:  %d\n", clients);
    printf("Numbers of Requests: %d\n", kRequests);
    
    // benchcore();
    // double seconds = responseTime / CLOCKS_PER_SEC;
    // printf("\nRequests: %d per second.\nResponse time: %lf ms\n",
    //         (int)((kRequests * clients) / seconds ),
    //         (double)(responseTime / kRequests) );

    double t;
    pid_t pid = 0;
    FILE* f;

    if (pipe(mypipe))
    {
        perror("pipe failed.");
        return 3;
    }

    /* fork childs */
    for(i = 0;i < clients; i++)
    {
        pid=fork();
        if(pid <= (pid_t) 0)
        {
            /* child process or error*/
            sleep(1); /* make childs faster */
            break;
        }
    }

    if( pid < (pid_t) 0)
    {
        // fprintf(stderr,"problems forking worker no. %d\n",i);
        perror("fork failed.");
        return 3;
    }

    if(pid == (pid_t) 0)
    {
        /* I am a child */
        benchcore();

        /* write results to pipe */
        f = fdopen(mypipe[1],"w");
        if(f==NULL)
        {
            perror("open pipe for writing failed.");
            return 3;
        }
        /* fprintf(stderr,"Child - %d %d\n",speed,failed); */
        fprintf(f,"%lf\n", responseTime);
        fclose(f);
        return 0;

    } else
    {
        int numClients = clients;
        f=fdopen(mypipe[0],"r");
        if(f==NULL) 
        {
            perror("open pipe for reading failed.");
            return 3;
        }
        setvbuf(f,NULL,_IONBF,0);

        responseTime=0;
        while(1)
        {
            pid=fscanf(f,"%lf",&t);
            if(pid<1)
            {
                fprintf(stderr,"Some of our childrens died.\n");
                break;
            }
            responseTime += t;
            /* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */
            if(--clients==0) break;
        }
        fclose(f);

        double seconds = responseTime / CLOCKS_PER_SEC;
        // printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n",
        //         (int)((speed+failed)/(benchtime/60.0f)),
        //         (int)(bytes/(float)benchtime),
        //         speed,
        //         failed);
        int qps = kRequests * numClients / seconds;
        double rt = responseTime / (kRequests * numClients);
        printf("\nRequests: %d per second.\nResponse time: %lf ms\n", qps, rt);
    }
    return 0;

}

// g++ -std=c++17 *.cpp ../*.cpp ../../*.cpp -lpthread -lprotobuf -o rpcbench
